package com.linshengjian.snowball.core;

import com.linshengjian.snowball.core.exception.BusinessException;
import com.linshengjian.snowball.core.exception.SystemErrorException;
import com.linshengjian.snowball.core.page.PageData;
import com.linshengjian.snowball.core.page.PageParam;
import com.linshengjian.snowball.utils.ObjectConvertUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

public class BaseService<
        ID extends Serializable,
        E extends BaseEntity<ID, M>,
        Dao extends BaseDao,
        M extends BaseModel<ID> > implements IReadService<M, ID>, IWriteService<M, ID> {

    @Autowired
    private ApplicationContext context;

    private Dao dao;

    protected Dao dao() {
        if (dao == null) {
            Type genType = getClass().getGenericSuperclass();
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            if (params[2] instanceof Class)
                dao = context.getBean((Class<Dao>) params[2]);
        }
        return dao;
    }

    protected Class<E> getEntityClass() {
        Type genType = getClass().getGenericSuperclass();
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        return (Class) params[1];
    }

    protected E model2Entity(M model) throws SystemErrorException {
        try {
            E entity = getEntityClass().newInstance();
            entity.assign(model);
            return entity;
        } catch (Exception e) {
            throw new SystemErrorException(e);
        }
    }

    @Deprecated
    protected List<M> entity2Model(Collection<E> list) throws SystemErrorException {
        return toModels(list);
    }

    @Deprecated
    protected PageData<M> page2pageResp(Page<E> page) throws SystemErrorException {
        return toModels(page);
    }

    protected <TID extends Serializable, TM extends BaseModel<TID>, TE extends BaseEntity<TID, TM>> PageData<TM> toModels(Page<TE> page) throws SystemErrorException {
        PageData<TM> pageResp = new PageData<>();
        List<TM> mList = toModels(page.getContent());
        pageResp.setList(mList);
        pageResp.setIndex(page.getNumber() + 1);
        pageResp.setSize(page.getSize());
        pageResp.setTotalPage(page.getTotalPages());
        pageResp.setTotalRow(page.getTotalElements());
        return pageResp;
    }

    protected <TID extends Serializable, TM extends BaseModel<TID>, TE extends BaseEntity<TID, TM>> List<TM> toModels(Collection<TE> list) throws SystemErrorException {
        List<TM> mList = new ArrayList<>();
        for (TE e : list) {
            mList.add(e.toModel());
        }
        return mList;
    }

    @Override
    public M add(M model) throws BusinessException, SystemErrorException {
        if (null != model.getId()) {
            throw new BusinessException("新增方法不能接受一个已经存在的对象");
        }

        try {
            E entity = ObjectConvertUtils.getInstance().assign(getEntityClass().newInstance(), model, true, true);
            entity = (E) dao().save(entity);
            return entity.toModel();
        } catch (Exception e) {
            throw new SystemErrorException(e);
        }
    }

    @Override
    public M update(M model) throws BusinessException, SystemErrorException {
        if (null == model.getId()) {
            throw new BusinessException("修改对象不能接受一个不存在的对象");
        }
        Optional<E> optional = dao().findById(model.getId());
        if (!optional.isPresent())
            throw new BusinessException("修改对象不能接受一个不存在的对象");

        E entity = (E) optional.get().assign(model, true);
        entity = (E) dao().save(entity);

        return entity.toModel();
    }

    @Override
    public void delete(ID id) throws BusinessException {
        dao().deleteById(id);
    }

    @Override
    public M get(ID id) throws SystemErrorException {
        Optional<E> e = dao().findById(id);
        if (e.isPresent())
            return e.get().toModel();
        return null;
    }

    @Override
    public List<M> list() throws SystemErrorException {
        return toModels(dao().findAll());
    }

    protected Predicate getVagueCondition(final String keyword, Root root, CriteriaQuery query, CriteriaBuilder builder) throws IllegalAccessException, InstantiationException {

        List<Predicate> where = new ArrayList<>();

        List<Field> fields = ObjectConvertUtils.getInstance().getFields(getEntityClass());
        for (Field field: fields) {
            if (field.getType().equals(String.class)) {
                where.add(builder.like(builder.upper(root.get(field.getName())), "%" + keyword.trim().toUpperCase() + "%"));
            }
        }

        return builder.or(where.toArray(new Predicate[where.size()]));
    }

    protected Specification<E> getSpecification(final String keywork) {
        return new Specification<E>() {
            @Override
            public Predicate toPredicate(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                try {
                    return getVagueCondition(keywork, root, query, cb);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
                return null;
            }
        };
    }

    @Override
    public List<M> list(final String keywork) throws SystemErrorException {
        return toModels(dao().findAll(getSpecification(keywork)));
    }

    @Override
    public PageData<M> list(PageParam pageReq) throws SystemErrorException {
        PageRequest pageRequest = PageRequest.of(pageReq.getIndex() - 1, pageReq.getSize(), new Sort(Sort.Direction.DESC, "createTime"));
        Page<E> page = dao().findAll(pageRequest);
        return toModels(page);
    }

    @Override
    public PageData<M> list(final String keywords, PageParam pageReq) throws SystemErrorException {
        PageRequest pageRequest = PageRequest.of(pageReq.getIndex() - 1, pageReq.getSize(), new Sort(Sort.Direction.DESC, "createTime"));
        Page<E> page = dao().findAll(getSpecification(keywords), pageRequest);
        return toModels(page);
    }
}